使用Unicorn Engine绕过混淆完成算法的调用
最近在研究用Unicorn Engine调用SO时,发现网上的资料很少,并且没有一个完整的调用实例。所以我把自己做的发出来跟大家分享,共同学习进步。
下面开始:
一、我们的目的
以上一串字符串中vf字段为标红部分的signature。该算法在libmcto_media_player.so+0x249BC8处。如果是Android端调用的话很简单,我们编写一个loader调用该函数传入参数获取返回值即可轻易拿到。但如果你想在Windows或linux上获取该signature就会比较麻烦。一般都是通过逆向还原代码来进行移植。但是如果遇见混淆或VM的代码,那将是痛苦的。所以这就是我为什么要介绍Unicorn
Engine的原因了。我们要用Unicorn Engine来完成跨平台的调用。
二、 用NDK编写loader用做验证用
#include <stdio.h>
#include <string.h>
#include <dlfcn.h>
#include <jni.h>
#include <stdlib.h>
int main(int argc,char** argv)
{
JavaVM* vm;
JNIEnv* env;
jint res;
JavaVMInitArgs vm_args;
JavaVMOption options[1];
options[0].optionString = "-Djava.class.path=.";
vm_args.version=0x00010002;
vm_args.options=options;
vm_args.nOptions =1;
vm_args.ignoreUnrecognized=JNI_TRUE;
printf("[+] dlopen libdvm.so\n");
void *handle = dlopen("/system/lib/libdvm.so", RTLD_LAZY);//RTLD_LAZY RTLD_NOW
if(!handle){
printf("[-] dlopen libdvm.so failed!!\n");
return 0;
}
typedef int (*JNI_CreateJavaVM_Type)(JavaVM**, JNIEnv**, void*);
JNI_CreateJavaVM_Type JNI_CreateJavaVM_Func = (JNI_CreateJavaVM_Type)dlsym(handle, "JNI_CreateJavaVM");
if(!JNI_CreateJavaVM_Func){
printf("[-] dlsym failed\n");
return 0;
}
res=JNI_CreateJavaVM_Func(&vm,&env,&vm_args);
//libmctocurl.so libcupid.so 为libmcto_media_player.so的依赖库
dlopen("/data/local/tmp/libmctocurl.so",RTLD_LAZY);
dlopen("/data/local/tmp/libcupid.so",RTLD_LAZY);
void* si=dlopen("/data/local/tmp/libmcto_media_player.so",RTLD_LAZY);
if(si == NULL)
{
printf("dlopen err!\n");
return 0;
}
typedef char* (*FUN1)(char* plain);
void *addr=(void*)(*(int*)((size_t)si+0x8c)+0x249BC9);
FUN1 func=(FUN1)addr;
if(func==NULL)
{
printf("can't find func\n");
return 0;
}
char *plain="/vps?tvid=11949478009&vid=7b23569cbed511dd58bcd6ce9ddd7b42&v=0&qypid=11949478009_unknown&src=02022001010000000000&tm=1519712402&k_tag=1&k_uid=359125052784388&bid=1&pt=0&d=1&s=0&rs=1&dfp=1413357b5efa4a4130b327995c377ebb38fbd916698ed95a28f56939e9d8825592&k_ver=9.0.0&k_ft1=859834543&k_err_retries=0&qd_v=1";
char* ret=func(plain);
printf("%s\n",ret);
return 0;
}
我之前已经将那3个so(libmctocurl.so、libcupid.so、libmcto_media_player.so) 放到/data/local/tmp下。运行结果与上面的vf字段一致。
三、 使用Unicorn Engine
由于使用了混淆。分析起来比较麻烦,所以使用Unicorn进行调用
#include "stdafx.h"
#include <inttypes.h>
#include <string.h>
#include <math.h>
#include <unicorn/unicorn.h>
#pragma comment(lib,"unicorn.lib")
//#define DEBUG
#define _DWORD uint32_t
#define LODWORD(x) (*((_DWORD*)&(x)))
#define HIDWORD(x) (*((_DWORD*)&(x)+1))
#define ADDRESS 0x249BC8
#define BASE 0xaef52000
#define CODE_SIZE 8*1024*1024
#define STACK_ADDR BASE+CODE_SIZE
#define STACK_SIZE 1024 * 1024
#define PARAM_ADDR STACK_ADDR+STACK_SIZE
#define PARAM_SIZE 1024 * 1024
uint32_t offset=0;
static uint32_t create_mem(uc_engine *uc,char* buffer,uint32_t len)
{
uint32_t addr = PARAM_ADDR + offset;
uc_mem_write(uc, addr, buffer, len);
offset += len + 1;
return addr;
}
static void print_reg(uc_engine *uc, uint32_t address)
{
#ifdef DEBUG
uint32_t pc = 0;
uc_reg_read(uc, UC_ARM_REG_PC, &pc);
if (pc == address)
{
printf("========================\n"); printf("Break on 0x%x\n", pc);
uint32_t values = 0;
uc_reg_read(uc, UC_ARM_REG_R0, &values); printf("R0 = 0x%x \n", values);
uc_reg_read(uc, UC_ARM_REG_R1, &values); printf("R1 = 0x%x \n", values);
uc_reg_read(uc, UC_ARM_REG_R2, &values); printf("R2 = 0x%x \n", values);
uc_reg_read(uc, UC_ARM_REG_R3, &values); printf("R3 = 0x%x \n", values);
uc_reg_read(uc, UC_ARM_REG_R4, &values); printf("R4 = 0x%x \n", values);
uc_reg_read(uc, UC_ARM_REG_R5, &values); printf("R5 = 0x%x \n", values);
uc_reg_read(uc, UC_ARM_REG_R6, &values); printf("R6 = 0x%x \n", values);
uc_reg_read(uc, UC_ARM_REG_PC, &values); printf("PC = 0x%x \n", values);
uc_reg_read(uc, UC_ARM_REG_SP, &values); printf("SP = 0x%x \n", values);
printf("========================\n");
}
#endif // DEBUG
}
static void hook_code(uc_engine *uc, uint64_t address, uint32_t size, void *user_data)
{
#ifdef DEBUG
printf(">>> Tracing instruction at 0x%" PRIx64 ", instruction size = 0x%x\n", address, size);
#endif // DEBUG
switch (address)
{
//strlen
case BASE + 0x249BEE:
{
uint32_t r0 = 0;
char buffer[4096] = "";
uc_reg_read(uc, UC_ARM_REG_R0, &r0);
uc_mem_read(uc, r0, buffer, 4096);
r0 = strlen(buffer);
uc_reg_write(uc, UC_ARM_REG_R0, &r0);
uint32_t pc = address;
pc += 5;
uc_reg_write(uc, UC_ARM_REG_PC, &pc);
break;
}
//malloc
case BASE+ 0x249f3c:
case BASE+ 0x249f06:
case BASE + 0x249c02:
{
uint32_t r0 = 0;
uc_reg_read(uc, UC_ARM_REG_R0, &r0);
char* buffer = (char*)malloc(r0);
r0=create_mem(uc, buffer, r0);
free(buffer);
uc_reg_write(uc, UC_ARM_REG_R0, &r0);
uint32_t pc = address;
pc += 5;
uc_reg_write(uc, UC_ARM_REG_PC, &pc);
break;
}
//memcpy 后为THUMB指令
case BASE+0x249c68:
case BASE+0x249c0e:
case BASE+0x24947A:
case BASE+0x249456:
{
uint32_t r0 = 0;
uint32_t r1 = 0;
uint32_t r2 = 0;
uc_reg_read(uc, UC_ARM_REG_R0, &r0);
uc_reg_read(uc, UC_ARM_REG_R1, &r1);
uc_reg_read(uc, UC_ARM_REG_R2, &r2);
char *buffer =(char*)malloc(r2);
uc_mem_read(uc, r1, buffer, r2);
uc_mem_write(uc, r0, buffer, r2);
free(buffer);
uint32_t pc = address;
//memcpy 后为ARM指令
if (address == BASE + 0x249c68)
pc += 4;
else
pc += 5;
uc_reg_write(uc, UC_ARM_REG_PC, &pc);
break;
}
//特殊处理4字ARM指令
case BASE + 0x249C6C:
{
uint32_t pc = address;
pc += 5;
uint32_t r0 = 0x2c0;
uc_reg_write(uc, UC_ARM_REG_R0, &r0);
uc_reg_write(uc, UC_ARM_REG_PC, &pc);
break;
}
//跳过stack_guard错误的内存地址
case BASE + 0x249BD8:
{
uint32_t pc = address;
pc += 7;
uc_reg_write(uc, UC_ARM_REG_PC, &pc);
break;
}
//sin函数
case BASE+0x249EE8:
{
uint32_t r0 = 0;
uint32_t r1 = 0;
uc_reg_read(uc, UC_ARM_REG_R0, &r0);
uc_reg_read(uc, UC_ARM_REG_R1, &r1);
double value = 0;
memcpy(&value, &r0, 4);
memcpy((char*)&value+4, &r1, 4);
double ret=sin(value);
r0 = LODWORD(ret);
r1 = HIDWORD(ret);
uc_reg_write(uc, UC_ARM_REG_R0, &r0);
uc_reg_write(uc, UC_ARM_REG_R1, &r1);
uint32_t pc = address;
pc += 5;
uc_reg_write(uc, UC_ARM_REG_PC, &pc);
break;
}
//free
case BASE+ 0x24a68c:
case BASE+0x249f24:
{
uint32_t pc = address;
pc += 5;
uc_reg_write(uc, UC_ARM_REG_PC, &pc);
}
default:
{
print_reg(uc, address);
break;
}
}
}
static unsigned char* read_file(char* path, uint32_t* len)
{
FILE* fp = fopen(path, "rb");
if (fp == NULL)
return nullptr;
fseek(fp, 0, SEEK_END);
*len = ftell(fp);
fseek(fp, 0, SEEK_SET);
unsigned char* code = (unsigned char*)malloc(*len);
memset(code, 0, *len);
fread(code, 1, *len, fp);
fclose(fp);
return code;
}
static void test_thumb(void)
{
uc_engine *uc;
uc_err err;
uc_hook trace1, trace2;
uint32_t sp = STACK_ADDR;
offset = 0;
err = uc_open(UC_ARCH_ARM, UC_MODE_THUMB, &uc);
if (err) {
printf("Failed on uc_open() with error returned: %u (%s)\n",
err, uc_strerror(err));
return;
}
char plain[] = "/vps?tvid=11949478009&vid=7b23569cbed511dd58bcd6ce9ddd7b42&v=0&qypid=11949478009_unknown&src=02022001010000000000&tm=1519712402&k_tag=1&k_uid=359125052784388&bid=1&pt=0&d=1&s=0&rs=1&dfp=1413357b5efa4a4130b327995c377ebb38fbd916698ed95a28f56939e9d8825592&k_ver=9.0.0&k_ft1=859834543&k_err_retries=0&qd_v=1";
uc_mem_map(uc, PARAM_ADDR, PARAM_SIZE, UC_PROT_ALL);
uc_mem_map(uc, BASE, CODE_SIZE, UC_PROT_ALL);
uint32_t r0 = PARAM_ADDR;
uint32_t sp_start = sp + STACK_SIZE;
int ret=uc_mem_map(uc, sp, sp_start - sp, UC_PROT_ALL);
uint32_t len = 0;
unsigned char* code = read_file("./aef52000_36e000.so", &len);
uc_mem_write(uc, BASE, code, len);
free(code);
create_mem(uc, plain, strlen(plain) + 1);
uc_reg_write(uc, UC_ARM_REG_R0, &r0);
uc_reg_write(uc, UC_ARM_REG_SP, &sp);
uc_hook_add(uc, &trace2, UC_HOOK_CODE, hook_code, NULL, 1, 0);
err = uc_emu_start(uc, BASE + 0x249BC8 + 1, BASE + 0x24a692, 0, 0);
if (err) {
printf("Failed on uc_emu_start() with error returned: %u\n", err);
}
char buffer[4096] = "";
uc_reg_read(uc, UC_ARM_REG_R0, &r0);
uc_mem_read(uc, r0, buffer, 4096);
printf("result:%s\n", buffer);
uc_close(uc);
}
int main()
{
test_thumb();
system("pause");
return 0;
}
代码已经给了,就不多说了,
我没有直接使用libmcto_media_player.so因为data段需要重定位。所以我写了一个dump工具。
将SO从内存中dump出来。直接调用这段已经重定位过的内存。
修复内存报错的位置。实现该算法中涉及的几个API 包括 strlen memcpy malloc free sin 函数。
主要就是注意BLX调用完API的时候下一条指令是THUMB模式还是ARM模式就好。
最后运行,运行结果也与vf字段一致。
dump通过命令
shell@hammerhead:/data/local/tmp $./dump ./libmcto_media_player.so ./libmctocurl.so ./libcupid.so
[+] dlopen ./libmctocurl.so
[+] dlopen ./libcupid.so
[+] dlopen libdvm.so
[+] save 0xaf009000_0x377000.so
四、总结
这只是一个简单的算法函数,涉及的API并不多,如果是复杂的算法涉及API数量庞大这种自己实现API的方式就并不可取。所以接下来有时间会继续研究SO的完整的调用。让他像loader一样方便。
五、参考
Android SO 高阶黑盒利用
挑战4个任务:迅速上手Unicorn Engine
本文由看雪论坛 scxc 原创
转载请注明来自看雪社区
热门阅读
点击阅读原文/read,
更多干货等着你~